package cn.com.zero.common.auth.config;


import cn.com.zero.common.auth.constant.SecurityConstants;
import cn.com.zero.common.auth.security.filter.MobileCodeAuthenticationFilter;
import cn.com.zero.common.auth.security.handler.LoginAuthenticationFailureHandler;
import cn.com.zero.common.auth.security.handler.LoginAuthenticationSuccessHandler;
import cn.com.zero.common.auth.security.handler.LogoutSuccessHandler;
import cn.com.zero.common.auth.security.provider.MobileCodeAuthenticationProvider;
import cn.com.zero.common.auth.security.service.AbstractLoginAuthenticationService;
import cn.com.zero.common.auth.security.service.impl.UserDetailsServiceMobile;
import cn.com.zero.common.auth.security.service.impl.UserDetailsServiceUserName;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

/**
 * @author xiongxj
 * @version 1.0.0
 * @Description Spring security 配置类
 * @createTime 2023/3/14 15:44
 */
public abstract class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private UserDetailsServiceMobile userDetailsServiceMobile;
    private UserDetailsServiceUserName userDetailsServiceUserName;
    private AbstractLoginAuthenticationService loginAuthenticationService;

    // 装配登录成功处理器。下方配置时不能用 new 的形式，不然里面的接口注入会报空指针
    private LoginAuthenticationSuccessHandler authenticationSuccessHandler;
    // 装配登录失败处理器。下方配置时不能用 new 的形式，不然里面的接口注入会报空指针
    private LoginAuthenticationFailureHandler authenticationFailureHandler;
    // 装配退出登录成功处理器。下方配置时不能用 new 的形式，不然里面的接口注入会报空指针
    private LogoutSuccessHandler logoutSuccessHandler;

    /**
     * 验证器加载
     *
     * @return 验证管理对象
     * @throws Exception
     */
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(mobileCodeAuthenticationProvider());
        auth.authenticationProvider(userNameAuthenticationProvider());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 添加手机验证码认证过滤器(authentication) ， 在 UsernamePasswordAuthenticationFilter 之前
        http.addFilterBefore(getMobileCodeAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

//        // 设置白名单（由于权限认证全部都放到了gateway模块进行验证，因此此处需要将所有路径放行）
//        http.authorizeRequests().antMatchers(getPermitUrl().toArray(new String[0])).permitAll()
//                // 其他地址的访问均需验证权限
//                .anyRequest().authenticated();

        // 由于系统采用前后端分离的方式开发，不再使用session，禁用CSRF
        http.csrf().disable();

        // 基于token认证，因此不需要session。这样就永远不会创建HttpSession，也永远不能获取SecurityContext。
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        // 放行所有的访问请求
        http.authorizeRequests().anyRequest().permitAll()
                .and()
                // 启用登录验证，调用此方法才会加入 UsernamePasswordAuthenticationFilter 过滤器
                .formLogin()
                // 设置没有登录时访问地址 (常规网页设置为登录页面)。若配置了loginPage，未配置loginProcessingUrl，那么默认的登录路径就是loginPage指定的路径
                // 注意，若放行了所有访问请求，未登陆的情况下并不会跳转到此路径下（退出登录除外，即未登录时退出会跳转至此路径下）。
                .loginPage(SecurityConstants.SECURITY_CONTROLLER_PATH + "/loginPage")
                // 设置 登录处理地址，实际上更改了 UsernamePasswordAuthenticationFilter 中默认的匹配路径
                .loginProcessingUrl("/login")
                // 设置自定义的 登录成功处理
                .successHandler(authenticationSuccessHandler)
                // 设置自定义的 登录失败处理
                .failureHandler(authenticationFailureHandler)
                .permitAll();

        http.logout()
                // 设置退出处理地址，官方建议使用Post请求退出登录
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout", "POST"))
                // 退出成功处理
                .logoutSuccessHandler(logoutSuccessHandler)
                .permitAll();


    }

    /**
     * 构建手机号验证码认证方式的provider对象
     *
     * @return 手机号验证码认证方式的provider对象
     */
    protected MobileCodeAuthenticationProvider mobileCodeAuthenticationProvider() {
        MobileCodeAuthenticationProvider provider = new MobileCodeAuthenticationProvider();
        provider.setUserDetailsService(userDetailsServiceMobile);
        provider.setLoginAuthenticationService(loginAuthenticationService);
        // 禁止隐藏 用户未找到异常
        provider.setHideUserNotFoundExceptions(false);
        return provider;
    }

    /**
     * 构建 用户名密码认证方式的provider对象
     *
     * @return 用户名密码认证方式的provider对象
     */
    protected DaoAuthenticationProvider userNameAuthenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        // 设置userDetailsService
        provider.setUserDetailsService(userDetailsServiceUserName);
        // 禁止隐藏 用户未找到异常
        provider.setHideUserNotFoundExceptions(false);
        // 使用BCrypt进行密码的hash
        provider.setPasswordEncoder(passwordEncoder());
        return provider;
    }

    /**
     * 获取使用手机验证码方式登录的 authentication 过滤器
     *
     * @return 过滤器 {@link MobileCodeAuthenticationFilter}
     */
    protected MobileCodeAuthenticationFilter getMobileCodeAuthenticationFilter() {
        MobileCodeAuthenticationFilter filter = new MobileCodeAuthenticationFilter();
        try {
            filter.setAuthenticationManager(this.authenticationManagerBean());
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
        // 设置successHandler与failureHandler，其他方式设置不生效
        filter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
        filter.setAuthenticationFailureHandler(authenticationFailureHandler);

        return filter;
    }

    /**
     * 用户名密码登录的密码加密方式
     *
     * @return 加密方式
     */
    protected PasswordEncoder passwordEncoder() {
        // 采用了SHA-256+随机盐+密钥对密码进行加密
        return new BCryptPasswordEncoder();
    }

    @Autowired
    public void setUserDetailsServiceMobile(UserDetailsServiceMobile userDetailsServiceMobile) {
        this.userDetailsServiceMobile = userDetailsServiceMobile;
    }

    @Autowired
    public void setUserDetailsServiceUserName(UserDetailsServiceUserName userDetailsServiceUserName) {
        this.userDetailsServiceUserName = userDetailsServiceUserName;
    }

    @Autowired
    public void setAuthenticationSuccessHandler(LoginAuthenticationSuccessHandler authenticationSuccessHandler) {
        this.authenticationSuccessHandler = authenticationSuccessHandler;
    }

    @Autowired
    public void setAuthenticationFailureHandler(LoginAuthenticationFailureHandler authenticationFailureHandler) {
        this.authenticationFailureHandler = authenticationFailureHandler;
    }

    @Autowired
    public void setLogoutSuccessHandler(LogoutSuccessHandler logoutSuccessHandler) {
        this.logoutSuccessHandler = logoutSuccessHandler;
    }

    @Autowired
    public void setLoginAuthenticationService(AbstractLoginAuthenticationService loginAuthenticationService) {
        this.loginAuthenticationService = loginAuthenticationService;
    }
}
